/**
 * Copyright 2014-2020  the original author or authors.
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.upb.webserver.account.manager;

import cn.hutool.core.collection.CollectionUtil;
import com.upb.webserver.common.enums.AccountStatusEnum;
import com.upb.webserver.common.enums.PlatformEnum;
import com.upb.webserver.common.enums.RoleTypeEnum;
import com.upb.webserver.common.exception.WebServerException;
import com.upb.webserver.common.pojo.account.manager.ReqAddManagerVO;
import com.upb.webserver.common.pojo.account.manager.ReqModifyManagerVO;
import com.upb.webserver.common.pojo.account.manager.RspManagerAccountVO;
import com.upb.webserver.common.pojo.base.BasePageResponse;
import com.upb.webserver.common.pojo.base.BaseResponse;
import com.upb.webserver.common.returncode.ConstantCode;
import com.upb.webserver.common.tools.JsonUtils;
import com.upb.webserver.common.tools.MybatisExampleTools;
import com.upb.webserver.dao.entity.*;
import com.upb.webserver.dao.mapper.TbApplicationMapper;
import com.upb.webserver.dao.mapper.TbManagerApplicationMapper;
import com.upb.webserver.dao.mapper.TbManagerMapper;
import com.upb.webserver.dao.mapper.TbSecurityMapper;
import com.upb.webserver.role.RoleService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RequestBody;

import javax.validation.Valid;
import java.util.*;
import java.util.stream.Collectors;

/**
 * services for manager account data.
 */
@Slf4j
@Service
public class ManagerService {
    @Autowired
    private TbManagerApplicationMapper managerApplicationMapper;
    @Autowired
    private TbApplicationMapper applicationMapper;
    @Autowired
    private RoleService roleService;
    @Autowired
    private TbManagerMapper managerMapper;
    @Qualifier(value = "bCryptPasswordEncoder")
    @Autowired
    private PasswordEncoder passwordEncoder;

    @Autowired
    private TbSecurityMapper securityMapper;

    /**
     * add account row.
     */
    @Transactional
    public TbManager addManager(String currentRoleName, ReqAddManagerVO param) {
        log.debug("start exec method [addManager]. currentRoleName:{} param:{}", currentRoleName, JsonUtils.objToString(param));

        if (!RoleTypeEnum.DEVOPS_ADMIN.getName().equalsIgnoreCase(currentRoleName))
            throw new WebServerException(ConstantCode.NOT_SUPPORT_OPERATE.attach("just master manager can add account"));

        // check account
        checkBeforeNewManager(param.getAccount(), param.getEmail());

        TbManager tbManager = new TbManager();
        BeanUtils.copyProperties(param, tbManager);
        tbManager.setPassword(passwordEncoder.encode(param.getPassword()));
        tbManager.setGmtCreate(new Date());
        tbManager.setGmtModified(new Date());

        //add manager
        managerMapper.insertSelective(tbManager);
        String pid = PlatformEnum.DEV_OPS.getId() + "";
        securityMapper.insertByInfos(pid, tbManager.getAccount(), UUID.randomUUID().toString(), UUID.randomUUID().toString());


        //add ManagerApplication
//        List<Integer> applicationIdList = param.getApplicationIdList();
//        if (applicationIdList != null && applicationIdList.size() > 0) {
//            ((ManagerService) AopContext.currentProxy()).addManagerApplication(tbManager.getId(), param.getApplicationIdList());
//        }

        TbManager tbManagerRsp = managerMapper.selectByPrimaryKey(tbManager.getId());
        tbManagerRsp.setPassword("");
        log.debug("success exec method [addManager].");
        return tbManagerRsp;
    }


    /**
     * @param managerId
     * @param applicationIdList
     */
    @Transactional
    public void addManagerApplication(int managerId, List<Integer> applicationIdList) {
        log.debug("start exec method [addManagerApplication]. managerId:{} applicationIdList:{}", managerId, JsonUtils.objToString(applicationIdList));
        for (Integer applicationId : applicationIdList) {
            TbApplication application = applicationMapper.selectByPrimaryKey(applicationId);
            if (Objects.isNull(application)) {
                log.warn("found record by applicationId:{}", applicationId);
                throw new WebServerException(ConstantCode.APPLICATION_ID_INVALID);
            }

            //insert into tb_manager_application
            TbManagerApplication tbManagerApplication = new TbManagerApplication();
            tbManagerApplication.setManagerId(managerId);
            tbManagerApplication.setApplicationId(applicationId);
            tbManagerApplication.setGmtCreate(new Date());
            tbManagerApplication.setGmtModified(new Date());
            managerApplicationMapper.insert(tbManagerApplication);
        }
    }

    /**
     * @param account
     * @param email
     */
    public void checkBeforeNewManager(String account, String email) {

//        if (!WebServerTools.matcher(RegexPattern.EMAIL, String.valueOf(email))) {
//            log.warn("email not match:{}", email);
//            throw new WebServerException(ConstantCode.EMAIL_NOT_MATCH);
//        }

        //check record
        TbManager tbManager = managerMapper.selectByAccount(account);
        if (tbManager != null) {
            log.warn("found record by account:{}", account);
            throw new WebServerException(ConstantCode.ACCOUNT_HAS_BEAN_IN_USED);
        }

        if (StringUtils.isNoneBlank(email) && managerMapper.selectByEmail(email) != null) {
            log.warn("found record by email");
            throw new WebServerException(ConstantCode.EMAIL_HAS_BEAN_IN_USED);
        }
    }


    /**
     * @param status
     * @param pageNumber
     * @param pageSize
     * @return
     */
    public BasePageResponse queryManagerByPage(int currentAccountId, String currentRoleName, Byte status, int pageNumber, int pageSize) {
        log.info("start exec method [queryManagerByPage]. currentRoleName:{} pageNumber:{} pageSize:{} status:{}", currentRoleName, pageNumber, pageSize, status);
        BasePageResponse pageResponse = BasePageResponse.initialize();
        //query param
        TbManagerExample example = MybatisExampleTools.initSamplePageExample(TbManagerExample.class, pageNumber, pageSize, new TbManager(status));

        if (RoleTypeEnum.API_MANAGER.getName().equalsIgnoreCase(currentRoleName)) {
            TbManagerExample.Criteria criteria = example.createCriteria();
            criteria.andIdEqualTo(currentAccountId);
            example.getOredCriteria().add(criteria);
        }

        pageResponse.setTotalCount(managerMapper.countByExample(example));
        if (pageResponse.getTotalCount() > 0) {
            //query
            //List<TbManager> accountList = managerMapper.selectByExample(example);
            List<TbManagerWithAppID> accountList = managerMapper.selectWithAppID(example);
            //convert to RspDeveloperVO
            List<RspManagerAccountVO> list = new ArrayList<>(accountList.size());
            for (TbManager manager : accountList) {
                RspManagerAccountVO obj = new RspManagerAccountVO();
                BeanUtils.copyProperties(manager, obj);
                list.add(obj);
            }
            pageResponse.setData(list);
        }

        log.info("success exec method [queryManagerByPage]. result:{}", JsonUtils.objToString(pageResponse));
        return pageResponse;
    }


    /**
     * @param param
     * @return
     */
    public BaseResponse modifyManagerAccount(@RequestBody @Valid ReqModifyManagerVO param) {
        log.info("start exec method [modifyManagerAccount]. param:{}", JsonUtils.objToString(param));
        TbManager tbManager = managerMapper.selectByPrimaryKey(param.getId());
        if (Objects.isNull(tbManager)) {
            log.warn("found record by id:{}", param.getId());
            throw new WebServerException(ConstantCode.INVALID_ACCOUNT_ID);
        }
        tbManager.setEmail(param.getEmail());
        tbManager.setPassword(passwordEncoder.encode(param.getPassword()));
        tbManager.setGmtCreate(new Date());
        tbManager.setGmtModified(new Date());

        managerMapper.updateByPrimaryKey(tbManager);
        log.info("success exec method [modifyManagerAccount].");
        return new BaseResponse(ConstantCode.SUCCESS);
    }


    /**
     * @param currentAccountId
     * @param oldAccountPwd
     * @param newAccountPwd
     */
    public void updatePassword(int currentAccountId, String oldAccountPwd, String newAccountPwd) {
        log.debug("start updatePassword. currentAccountId:{} oldAccountPwd:{} newAccountPwd:{}",
                currentAccountId, oldAccountPwd, newAccountPwd);

        // query target account info
        TbManager targetRow = managerMapper.selectByPrimaryKey(currentAccountId);

        if (StringUtils.equals(oldAccountPwd, newAccountPwd)) {
            log.warn("fail updatePassword. the new password cannot be same as old ");
            throw new WebServerException(ConstantCode.NEW_PWD_EQUALS_OLD);
        }

        // check old password
        if (!passwordEncoder.matches(oldAccountPwd, targetRow.getPassword())) {
            throw new WebServerException(ConstantCode.PASSWORD_ERROR);
        }

        // update password
        targetRow.setPassword(passwordEncoder.encode(newAccountPwd));
        targetRow.setStatus(AccountStatusEnum.NORMAL.getId());
        Integer affectRow = managerMapper.updateByPrimaryKey(targetRow);

        log.debug("end updatePassword. affectRow:{}", affectRow);
    }

    /**
     * @param managerId
     */
    @Transactional
    public void deleteByManagerId(int managerId) {
        log.info("start exec method [deleteByManagerId].  managerId:{}", managerId);
        TbManager tbManager = managerMapper.selectByPrimaryKey(managerId);
        if (Objects.isNull(tbManager)) {
            log.warn("fail exec method [deleteByManagerId]. not found record by managerId:{}", managerId);
            throw new WebServerException(ConstantCode.INVALID_ACCOUNT_ID);
        }

        managerMapper.deleteByPrimaryKey(managerId);
        securityMapper.deleteByRelatedKey(String.valueOf(PlatformEnum.DEV_OPS.getId()), tbManager.getAccount());

        log.info("success exec method [deleteByManagerId].  managerId:{}", managerId);
    }

    /**
     * @param platformEnum
     * @return
     */
    public List<Integer> listIdByPlatForm(PlatformEnum platformEnum) {
        log.info("start exec method [listIdByPlatForm].  platform:{}", platformEnum.getId());
        Set<Integer> roleIdSet = roleService.getAllRoleIdByPlatform(platformEnum.getId());
        List<TbManager> managerList = managerMapper.listByRoleIdList(CollectionUtil.newArrayList(roleIdSet));
        List<Integer> managerIdList = managerList.stream().map(m -> m.getId()).collect(Collectors.toList());
        log.info("success exec method [deleteByManagerId].  managerIdList:{}", managerIdList);
        return managerIdList;
    }

    /**
     * @param platform
     * @param account
     * @return
     */
    public TbManager getByPlatformAndAccount(byte platform, String account) {
        log.info("start exec method [getByPlatformAndAccount].  platform:{} account:{}", platform, account);
        Set<Integer> roleIdSet = roleService.getAllRoleIdByPlatform(platform);

        TbManagerExample example = new TbManagerExample();
        TbManagerExample.Criteria criteria = example.createCriteria();
        criteria.andAccountEqualTo(account);
        criteria.andRoleIdIn(CollectionUtil.newArrayList(roleIdSet));
        TbManager tbManager = managerMapper.getOneByExample(example).orElse(null);

        log.info("success exec method [getByPlatformAndAccount].  platform:{} account:{} result:{}", platform, account, JsonUtils.objToString(tbManager));
        return tbManager;
    }
}
